use blake2::digest::InvalidOutputSize;
use clap::{App, Arg, ArgMatches};
mod key;
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use key::{Key32, NewKey};
mod nonce;
use nonce::{NewNonce, Nonce24};
mod file_ciph;
use file_ciph::{xchacha20_file_dec, xchacha20_file_enc};
use std::{ffi::OsStr, fs::File, io::{BufReader, BufWriter, Error, Read}, ops::Deref, path::{Path, PathBuf}};
fn main() {
    let app = App::new("blink-file-crypt")
        .version("0.1.0")
        .about("encrypt and decrypt file")
        .subcommand(
            App::new("encrypt")
                .about("encrypt file, use \"enc\" for short")
                .alias("enc")
                .arg(
                    Arg::with_name("input")
                        .short("i")
                        .long("input")
                        .required(true)
                        .takes_value(true)
                        .help("input file"),
                )
                .arg(
                    Arg::with_name("output")
                        .short("o")
                        .long("output")
                        .takes_value(true)
                        .help("output file"),
                )
                .arg(
                    Arg::with_name("password")
                        .short("p")
                        .long("password")
                        .takes_value(true)
                        .help("password for encrypt file"),
                )
                .arg(
                    Arg::with_name("key")
                        .short("k")
                        .long("key")
                        .takes_value(true)
                        .help("key path of the key for encrypt file"),
                )
                .arg(
                    Arg::with_name("enc_name")
                        .long("enc_name")
                        .help("use base64 to encode name"),
                ),
        )
        .subcommand(
            App::new("decrypt")
                .about("decrypt file, use \"dec\" for short")
                .alias("dec")
                .arg(
                    Arg::with_name("input")
                        .short("i")
                        .long("input")
                        .required(true)
                        .takes_value(true)
                        .help("input file"),
                )
                .arg(
                    Arg::with_name("output")
                        .short("o")
                        .long("output")
                        .takes_value(true)
                        .help("output file"),
                )
                .arg(
                    Arg::with_name("password")
                        .short("p")
                        .long("password")
                        .takes_value(true)
                        .help("password for encrypt file"),
                )
                .arg(
                    Arg::with_name("key")
                        .short("k")
                        .long("key")
                        .takes_value(true)
                        .help("key path of the key for encrypt file"),
                )
                .arg(
                    Arg::with_name("dec_name")
                        .long("dec_name")
                        .help("use base64 to decode name"),
                ),
        )
        .get_matches();
    if let Some(cmd) = app.subcommand_matches("encrypt") {
        handle_encrypt(cmd);
    }
    if let Some(cmd) = app.subcommand_matches("decrypt") {
        handle_decrypt(cmd);
    }
}

fn handle_encrypt(cmd: &ArgMatches) {
    // prepare arguments
    let input = Path::new(cmd.value_of("input").expect("input path is reqired"));
    let mut output = PathBuf::from(cmd.value_of("output").unwrap_or(""));
    let password = cmd.value_of("password").unwrap_or("");
    let key_path = cmd.value_of("key").unwrap_or("");
    let enc_name = cmd.is_present("enc_name");

    // prepare key
    let key = if password != "" {
        Key32::new_from_password(password).expect("password hash error")
    } else if key_path != "" {
        let key = Key32::new().expect("key generate error");
        key.save_to_file(key_path).expect("key save error");
        key
    } else {
        panic!("password or key is required");
    };

    // prepare nonce
    let nonce = Nonce24::new().expect("nonce generate error");

    // prepare output
    if output.as_os_str().is_empty() {
        output = input.to_path_buf();
    }

    // handle enc_name
    if enc_name {
        let fname = output
            .file_name()
            .expect("output file name error")
            .to_str()
            .unwrap();
        let fname = encode_config(fname, URL_SAFE_NO_PAD);
        output.set_file_name(fname);
    };
    println!("{:?}", output);
    let ext = output.extension().unwrap_or(OsStr::new("")).to_str().unwrap().to_string();
    if ext != ""{
        output.set_extension(format!("{}.{}", ext, "enc"));
    }else {
        output.set_extension("enc");
        
    }

    println!("{:?}", input);
    println!("{:?}", output);
    println!("{:?}", key);
    println!("{:?}", nonce);

    let mut input = BufReader::new(File::open(input).expect("input file open error"));
    let mut output = BufWriter::new(File::create(output).expect("output file create error"));
    xchacha20_file_enc(&mut input, &key, &nonce, &mut output).expect("encrypt error");
}

fn handle_decrypt(cmd: &ArgMatches) {
    // prepare arguments
    let input = Path::new(cmd.value_of("input").expect("input path is reqired"));
    let mut output = PathBuf::from(cmd.value_of("output").unwrap_or(""));
    let password = cmd.value_of("password").unwrap_or("");
    let key_path = cmd.value_of("key").unwrap_or("");
    let dec_name = cmd.is_present("dec_name");

    // prepare key
    let key = if password != "" {
        Key32::new_from_password(password).expect("password hash error")
    } else if key_path != "" {
        Key32::new_from_file(key_path).expect("key load error")
    } else {
        panic!("password or key is required");
    };

    // prepare output
    if output.as_os_str().is_empty() {
        output = input.deref().to_path_buf();
    }
    // let ext = output.extension().unwrap().to_str().unwrap().to_string();
    // let ext = ext.replace(".enc", "");
    // println!("{:?}", ext);
    output.set_extension("");

    // handle dec_name
    if dec_name {
        let fname = output
            .file_name()
            .expect("output file name error")
            .to_str()
            .unwrap();
        let fname =
            String::from_utf8(decode_config(fname, URL_SAFE_NO_PAD).expect("base64 decode error"))
                .expect("utf8 decode error");
        output.set_file_name(fname);
    };

    println!("{:?}", input);
    println!("{:?}", output);

    let mut input = BufReader::new(File::open(input).expect("input file open error"));
    let mut output = BufWriter::new(File::create(output).expect("output file create error"));
    xchacha20_file_dec(&mut input, &key, &mut output).expect("decrypt error");
}
